package cn.sylinx.hbatis.ext.proxy.command;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import cn.sylinx.hbatis.exception.HbatisException;
import cn.sylinx.hbatis.ext.ctxds.DynamicDatasourceProvider;
import cn.sylinx.hbatis.ext.proxy.annotation.Command;
import cn.sylinx.hbatis.ext.proxy.annotation.Datasource;
import cn.sylinx.hbatis.ext.proxy.annotation.PageNumber;
import cn.sylinx.hbatis.ext.proxy.annotation.PageSize;
import cn.sylinx.hbatis.ext.proxy.annotation.Param;
import cn.sylinx.hbatis.ext.proxy.annotation.ParamBean;
import cn.sylinx.hbatis.ext.proxy.annotation.Resource;
import cn.sylinx.hbatis.kit.BeanUtil;
import cn.sylinx.hbatis.kit.Pair;
import cn.sylinx.hbatis.kit.StrKit;
import cn.sylinx.hbatis.kit.Tuple;

class MethodMetadata {

	private static final Map<String, MethodMetadata> cachedMethod = new HashMap<>();

	public static MethodMetadata get(Class<?> commandInterface, Method method) {
		String key = commandInterface.getName() + "." + method.toString();
		MethodMetadata methodMetadata = get(key);
		if (methodMetadata == null) {
			methodMetadata = setAndGet(key, commandInterface, method);
		}
		return methodMetadata;
	}

	private static MethodMetadata get(String key) {
		return cachedMethod.get(key);
	}

	private static MethodMetadata setAndGet(String key, Class<?> commandInterface, Method method) {

		synchronized (key.intern()) {

			MethodMetadata methodMetadata = get(key);
			if (methodMetadata != null) {
				return methodMetadata;
			}

			methodMetadata = new MethodMetadata(commandInterface, method);
			cachedMethod.put(key, methodMetadata);
			return methodMetadata;
		}
	}

	private Class<?> commandInterface;
	private Command commandAnnotation;
	private Method method;
	private Class<?> truelyReturnType;
	private Annotation[][] parameterAnnotations;
	private Resource resource;
	private String truelyDatasource;
	private Class<?> returnType;
	// 是否使用缓存
	private boolean useCache = false;
	// 是否用native sql
	private boolean nativeSql = false;

	public MethodMetadata(Class<?> commandInterface, Method method) {
		this.commandInterface = commandInterface;
		this.method = method;
		readMetadata();
	}

	private void readMetadata() {
		// 类全局注解
		this.commandAnnotation = readCommandAnnotation();
		// 返回类型
		this.returnType = readReturnType();
		// 获取返回参数
		this.truelyReturnType = readTruelyReturnType();
		// 参数的所有注解
		this.parameterAnnotations = readParameterAnnotations();
		// 注解
		this.resource = readAndCheckResource();
		// 全局数据源
		this.truelyDatasource = readTruelyDatasource();
		// 缓存
		this.useCache = this.resource.useCache();
		// native sql
		this.nativeSql = this.resource.nativeSql() != null && !"".equals(this.resource.nativeSql().trim());
	}

	private Class<?> readReturnType() {
		return method.getReturnType();
	}

	private Command readCommandAnnotation() {
		return commandInterface.getAnnotation(Command.class);
	}

	private Annotation[][] readParameterAnnotations() {
		return method.getParameterAnnotations();
	}

	private Resource readAndCheckResource() {

		Resource resource = method.getAnnotation(Resource.class);
		if (resource == null) {
			throw new HbatisException("sql resource is empty");
		}

		String sqlResource = resource.sql();
		String nativeSql = resource.nativeSql();

		boolean isSqlResourceNull = sqlResource == null || "".equals(sqlResource.trim());
		boolean isNativeSqlNull = nativeSql == null || "".equals(nativeSql.trim());

		if (isSqlResourceNull && isNativeSqlNull) {
			throw new HbatisException("SQL资源和本地SQL不能都为空");
		}

		return resource;
	}

	/**
	 * 解析参数
	 * 
	 * @param args
	 * @return Tuple[0]:查询参数, Tuple[1]:pageNumber, Tuple[2]:pageSize,
	 *         Tuple[3]:datasource
	 */
	@SuppressWarnings("unchecked")
	public Tuple resove(Object[] args) {

		Integer pageNumber = 0, pageSize = 0;
		// 所有Param参数列表
		List<Pair> paramList = new ArrayList<>();
		// 所有map
		List<Map<String, Object>> mapList = new ArrayList<>();
		// 所有对象类型
		List<Object> beanList = new ArrayList<>();
		// 数据源判断
		List<String> datasourceList = new ArrayList<>();

		for (int i = 0; i < parameterAnnotations.length; ++i) {
			Annotation[] ats = parameterAnnotations[i];
			if (ats != null && ats.length > 0) {

				if (ats[0].annotationType() == PageNumber.class) {
					pageNumber = (Integer) args[i];
				}

				if (ats[0].annotationType() == PageSize.class) {
					pageSize = (Integer) args[i];
				}

				if (ats[0].annotationType() == Param.class) {
					paramList.add(Pair.apply(ats[0], args[i]));
				}

				if (ats[0].annotationType() == ParamBean.class) {
					beanList.add(args[i]);
				}

				if (ats[0].annotationType() == Datasource.class) {
					Object dsObject = args[i];
					if (dsObject != null) {
						if (dsObject.getClass() != String.class) {
							// 非字符串类型
							throw new HbatisException("数据源类型错误");
						}
						datasourceList.add(dsObject.toString());
					}

				}
			}

			if (args[i] != null && Map.class.isAssignableFrom(args[i].getClass())) {
				mapList.add((Map<String, Object>) args[i]);
			}
		}

		if (datasourceList.size() > 1) {
			throw new HbatisException("不能配置多个数据源");
		}

		String datasource = null;
		if (!datasourceList.isEmpty()) {
			datasource = datasourceList.get(0);
		}
		if (datasource == null) {
			datasource = truelyDatasource;
		}

		Map<String, Object> ps = new HashMap<>();

		// 遍历Param参数
		paramList.forEach(p -> {
			Param param = p.getObject(0);
			ps.put(param.value(), p.getObject(1));
		});

		// 遍历所有map
		mapList.forEach(map -> map.entrySet().forEach(entry -> ps.put(entry.getKey(), entry.getValue())));

		// 遍历所有bean
		beanList.forEach(bean -> ps.putAll(BeanUtil.bean2map(bean)));

		// 如果pageNumber,pageSize 为空，则从参数中获取
		if (pageNumber == null || pageNumber < 1) {
			pageNumber = Optional.ofNullable((Integer) ps.get("pageNumber")).orElse(0);
			pageSize = Optional.ofNullable((Integer) ps.get("pageSize")).orElse(0);
		}

		// TODO,此处判断动态数据源
		if (commandAnnotation.dynamic()) {
			// 如果是动态数据源，则切换
			datasource = DynamicDatasourceProvider.create().getDatasource();
		}

		return Tuple.apply(ps, pageNumber, pageSize, datasource);
	}

	private String readTruelyDatasource() {

		String datasourcePre = commandAnnotation.datasource().trim();
		String globalDatasource = StrKit.isEmpty(datasourcePre) ? null : datasourcePre;
		String truelyDatasource = globalDatasource;
		String datasource = resource.datasource().trim();
		if (datasource != null && !"".equals(datasource)) {
			truelyDatasource = datasource;
		}
		return truelyDatasource;
	}

	private Class<?> readTruelyReturnType() {

		Type genericReturnType = method.getGenericReturnType();
		// 获取返回值的泛型参数
		if (genericReturnType instanceof ParameterizedType) {
			Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
			return (Class<?>) actualTypeArguments[0];
		}

		return Object.class;
	}

	public Class<?> getTruelyReturnType() {
		return truelyReturnType;
	}

	public Resource getResource() {
		return resource;
	}

	public Class<?> getCommandInterface() {
		return commandInterface;
	}

	public Command getCommandAnnotation() {
		return commandAnnotation;
	}

	public Method getMethod() {
		return method;
	}

	public Class<?> getReturnType() {
		return returnType;
	}

	public boolean isUseCache() {
		return useCache;
	}

	public boolean isNativeSql() {
		return nativeSql;
	}

}
